import { getExtensionField } from "./helpers/getExtensionField.ts";
import type { AnyConfig } from "./types.ts";
import { callOrReturn } from "./utilities/callOrReturn.ts";
import { mergeDeep } from "./utilities/mergeDeep.ts";
import type { MarkConfig } from "@valuation/core";

export class Mark<Options = any, Storage = any> {
  type = "mark";

  name = "mark";

  parent: Mark | null = null;

  child: Mark | null = null;

  options: Options;

  storage: Storage;

  config: MarkConfig = {
    name: this.name,
    defaultOptions: {},
  };

  constructor(config: Partial<MarkConfig<Options, Storage>> = {}) {
    this.config = {
      ...this.config,
      ...config,
    };

    this.name = this.config.name;

    if (config.defaultOptions) {
      console.warn(
        `[rivet warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${this.name}".`,
      );
    }

    // TODO: remove `addOptions` fallback
    this.options = this.config.defaultOptions;

    if (this.config.addOptions) {
      this.options = callOrReturn(getExtensionField<AnyConfig["addOptions"]>(
        this,
        "addOptions",
        {
          name: this.name,
        },
      ));
    }

    this.storage = callOrReturn(getExtensionField<AnyConfig["addStorage"]>(
      this,
      "addStorage",
      {
        name: this.name,
        options: this.options,
      },
    )) || {};
  }

  static create<O = any, S = any>(
    config: Partial<MarkConfig<O, S>> = {},
  ): Mark {
    return new Mark<O, S>(config);
  }

  configure(options: Partial<Options> = {}): Mark {
    // return a new instance so we can use the same extension
    // with different calls of `configure`
    const extension = this.extend();
    // @ts-ignore
    extension.options = mergeDeep(this.options, options) as Options;

    extension.storage = callOrReturn(getExtensionField<AnyConfig["addStorage"]>(
      extension,
      "addStorage",
      {
        name: extension.name,
        options: extension.options,
      },
    ));

    return extension;
  }

  extend<ExtendedOptions = Options, ExtendedStorage = Storage>(
    extendedConfig: Partial<MarkConfig<ExtendedOptions, ExtendedStorage>> = {},
  ): Mark {
    const extension = new Mark<ExtendedOptions, ExtendedStorage>(
      extendedConfig,
    );

    extension.parent = this;

    this.child = extension;

    extension.name = extendedConfig.name
      ? extendedConfig.name
      : extension.parent.name;

    if (extendedConfig.defaultOptions) {
      console.warn(
        `[rivet warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`,
      );
    }

    extension.options = callOrReturn(getExtensionField<AnyConfig["addOptions"]>(
      extension,
      "addOptions",
      {
        name: extension.name,
      },
    ));

    extension.storage = callOrReturn(getExtensionField<AnyConfig["addStorage"]>(
      extension,
      "addStorage",
      {
        name: extension.name,
        options: extension.options,
      },
    ));

    return extension;
  }
}
